// this code was copied from cprogramming.com
// my respects for posting this code with 
// no official whereabouts as to where it was from

#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>
#include <stdarg.h>
#include <math.h>
#include <glut.h>

#include "Permutation.h"

#pragma warning(disable : 4996)


#define TWO_PI 6.283185307179586476925286766559
#define PI 3.1415926535897932384626433832795

typedef struct t_window
{
	float width;
	float height;
}WINDOW;

WINDOW window;

Permutation perm;

//t_point points[100];	// holds the coordinates of the points
//int path[101];			// holds indexes of the points array, describes the sequence of the arches
//GLfloat gWindowWidth, gWindowHeight;	// window dims in pixels
//int gNumPoints=20;
//int gNumArches=20;		// normally, its the same as gNumPoints except noted otherwise by user
bool bDrawBigPermutation=true;
bool bDrawVerticalLine=true;	// flag to draw or not the big vertical dashed line
char lastFileLoaded[256];		// filename of the last file loaded (for reload)
//
// the indexes of points between of which will draw the vertical line
// Actually, only the first one is needed.....
int gVertLinePoint1=9;			
int gVertLinePoint2=10;

GLvoid *gpfontStyle = GLUT_BITMAP_TIMES_ROMAN_10;

void initPoints( void );


void initDefaultPerm(void)
{
	//gNumPoints=20;
	//gNumArches=20;

	perm.AddArch(1);
	perm.AddArch(18);
	perm.AddArch(19);
	perm.AddArch(20);
	perm.AddArch(17);
	perm.AddArch(14);
	perm.AddArch(9);
	perm.AddArch(8);
	perm.AddArch(15);
	perm.AddArch(16);
	perm.AddArch(3);
	perm.AddArch(4);
	perm.AddArch(7);
	perm.AddArch(10);
	perm.AddArch(13);
	perm.AddArch(12);
	perm.AddArch(11);
	perm.AddArch(6);
	perm.AddArch(5);
	perm.AddArch(2);
	perm.AddArch(1);

	gVertLinePoint1=10;

	perm.InitPoints(20,window.width);

}

// void test2(void)
// {
// 	gNumPoints=20;
// 	gNumArches=19;
// 
// 	path[0]=1;
// 	path[1]=20;
// 	path[2]=2;
// 	path[3]=19;
// 	path[4]=3;
// 	path[5]=18;
// 	path[6]=4;
// 	path[7]=17;
// 	path[8]=5;
// 	path[9]=16;
// 	path[10]=6;
// 	path[11]=15;
// 	path[12]=7;
// 	path[13]=14;
// 	path[14]=8;
// 	path[15]=13;
// 	path[16]=9;
// 	path[17]=12;
// 	path[18]=10;
// 	path[19]=11;
// 	//path[20]=1;
// 
// 	initPoints();
// }

void readPermFromFile(std::string filename)
{
	std::ifstream input;
	std::string line;
	int numPoints=0;
	int numArches=0;
	int arch;

	input.open(filename.c_str());

	if (input.is_open())
	{
		// file is open
		// reset variables and load new ones
		bDrawVerticalLine=false;
		perm.Clear();

		//memset(path,0,sizeof(path));

		while (!input.eof())
		{
			getline(input,line);
			std::cout << line << std::endl;

			// skip comments and blanc lines
			if(line.length()<1 || line[0]=='#')
				continue;	

			if (0==line.compare("[points]"))
			{
				input >> numPoints;
				std::cout << "num points = " << numPoints << std::endl;
				continue;
			}

			if (0==line.compare("[arches]"))
			{
				input >> numArches;
				std::cout << "num arches = " << numArches << std::endl;
				continue;
			}

			if (0==line.compare("[path]"))
			{
				
				for (int i=0; i<=numArches; i++)
				{
					input >> arch;
					perm.AddArch(arch);
				}

				std::cout << "the path: ";
				for (int i=0; i<=numArches; i++)
				{
					std::cout << perm.path[i] << " ";
				}
				std::cout << std::endl;

				continue;
			}

			if (0==line.compare("[vertical line after point]"))
			{
				bDrawVerticalLine=true;	
				input >> gVertLinePoint1;
				std::cout << "vertical line will be draw after point " << gVertLinePoint1 << std::endl;
				continue;
			}

		}
		input.close();
	}
	else
	{
		std::cout << "COuld not open file..." << std::endl;
	}

	perm.InitPoints(numPoints, window.width);	
}


void drawDashedLine(GLfloat x1,GLfloat y1,GLfloat x2,GLfloat y2)
{
	glLineStipple(1,0xFF00);
	glEnable( GL_LINE_STIPPLE );  

	glBegin(GL_LINES);
	{
		glVertex2f(x1,y1);
		glVertex2f(x2,y2);
	}	
	glEnd();
	//glLineStipple(1,0xFFFF);
	glDisable(GL_LINE_STIPPLE);
}

void drawVertLine(const t_point *pt1, const t_point* pt2)
{
	GLfloat lineY = (window.height/2)-50;
	GLfloat lineX = pt1->x + ((pt2->x-pt1->x)/2.0f);

	drawDashedLine(lineX,lineY,lineX, -lineY);
}

void setFont(char* name, int size)
{
	gpfontStyle = GLUT_BITMAP_HELVETICA_10;
	if (strcmp(name, "helvetica") == 0) {
		if (size == 12) 
			gpfontStyle = GLUT_BITMAP_HELVETICA_12;
		else if (size == 18)
			gpfontStyle = GLUT_BITMAP_HELVETICA_18;
	} else if (strcmp(name, "times roman") == 0) {
		gpfontStyle = GLUT_BITMAP_TIMES_ROMAN_10;
		if (size == 24)
			gpfontStyle = GLUT_BITMAP_TIMES_ROMAN_24;
	} else if (strcmp(name, "8x13") == 0) {
		gpfontStyle = GLUT_BITMAP_8_BY_13;
	} else if (strcmp(name, "9x15") == 0) {
		gpfontStyle = GLUT_BITMAP_9_BY_15;
	}
}

void drawStr(GLuint x, GLuint y, char* format, ...)
{
	va_list args;
	char buffer[255], *s;

	va_start(args, format);
	vsprintf(buffer, format, args);
	va_end(args);

	glRasterPos2i(x, y);
	for (s = buffer; *s; s++)
		glutBitmapCharacter(gpfontStyle, *s);
}

void initPoints( void )
{
// 	gNumPoints=numPoints;
// 		float totalLineLength = window.width-100;
// 		float step = totalLineLength/(gNumPoints-1);
// 		points[0].x = -(window.width/2 - 50);
// 		points[0].y =  0;
// 		std::cout << "point 0 [" << points[0].x << "," << points[0].y << "]" << std::endl;
// 		for(int i=1; i<gNumPoints; i++)
// 		{
// 			points[i].x = points[i-1].x + step;
// 			points[i].y = 0;
// 			std::cout << "point " << i << "[" << points[i].x << "," << points[i].y << "]" << std::endl;
// 		}	
}

void Reload(void)
{
	if(strlen(lastFileLoaded)<1)
		return;
	readPermFromFile(lastFileLoaded);
}

void drawArc(int idx1,int idx2, bool up, int steps)
{
	GLfloat x1,y1,x2,y2,tx,ty,txPrev,tyPrev,startx;
	GLfloat pointx, pointy;
	bool intersectsVertLine=false;
	//GLfloat vertLineX;

// 	if (bDrawVerticalLine && bDrawBigPermutation)
// 		pointx = (GLfloat)points[9].x + (GLfloat)(points[10].x-points[9].x)/2.0f;
// 	else if (bDrawVerticalLine && !bDrawBigPermutation)
// 		pointx = (GLfloat)points[5].x + (GLfloat)(points[6].x-points[5].x)/2.0f;
	if (bDrawVerticalLine)
	{
		pointx = (GLfloat)perm.points[gVertLinePoint1-1].x + (GLfloat)(perm.points[gVertLinePoint1].x-perm.points[gVertLinePoint1-1].x)/2.0f;
	}
		

	x1 = perm.points[idx1].x;
	y1 = perm.points[idx1].y;
	x2 = perm.points[idx2].x;
	y2 = perm.points[idx2].y;
	startx = (x1<x2)?x1:x2;	//check whether we draw backward or forward

	float radius = (float)sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) )*0.5f;

	double angleStep = PI/steps;	
	double angle=0.0;
	float yFactor = (up)?0.7f:-0.7f;

	glBegin(GL_LINE_LOOP);
	{
		glVertex2f(startx,y1);
		txPrev = startx;
		tyPrev = y1;
				
		for(int i=0; i<steps; i++)
		{
			angle += angleStep;
			tx = startx+(1-cos(angle))*radius;
			ty = yFactor*(y1+sin(angle)*radius);
			if( txPrev<=pointx && pointx<tx )
			{
				intersectsVertLine=true;
				pointy = tyPrev + (ty-tyPrev)/2 - ((up)?3:0);	// somewhere in the middle
			}
			glVertex2f(tx, ty);
			
			txPrev = tx;
			tyPrev = ty;
		}
	}
	glEnd();

	if(bDrawVerticalLine && intersectsVertLine)
	{
		glBegin(GL_POINTS);
		glVertex2f(pointx,pointy);
		glEnd();
	}
}

void drawPermutation(void)
{
	bool up=true;

	int numPoints = perm.NumPoints();

	glBegin(GL_LINES);
	glVertex2f(perm.points[0].x, perm.points[0].y);
	glVertex2f(perm.points[numPoints-1].x, perm.points[numPoints-1].y);
	glEnd();

	// draw the point numbering
	setFont("helvetica",10);
	for (int i=0; i<numPoints; i++)
	{
		drawStr(perm.points[i].x+5, perm.points[i].y+5,"%d",i+1);
	}

	// draw a small circle for each point
	glBegin(GL_POINTS);
	{
		for (int i=0; i<numPoints; i++)
		{
			glVertex2f(perm.points[i].x,perm.points[i].y);
		}
	}
	glEnd();

#define DRAW_ARCH(id1,id2,up) drawArc(id1-1,id2-1,up,20) //1-based numbering

	for (int i=0; i<perm.NumArches()-1; i++)
	{
		DRAW_ARCH( perm.path[i], perm.path[i+1], up );
		up = !up;	// next arch will be the other side
	}

	if(bDrawVerticalLine)	// -1 because gVertLinePoint1 is 1-based
		drawVertLine( &perm.points[gVertLinePoint1-1], &perm.points[gVertLinePoint1] );
}

void drawBigPermutation(void)
{
// 	glBegin(GL_LINES);
// 	glVertex2f(points[0].x, points[0].y);
// 	glVertex2f(points[19].x, points[19].y);
// 	glEnd();
// 
// 	// draw the point numbering
// 	setFont("helvetica",10);
// 	for (int i=0; i<20; i++)
// 	{
// 		drawStr(points[i].x+5, points[i].y+5,"%d",i+1);
// 	}
// 
// 	// draw a small circle for each point
// 	glBegin(GL_POINTS);
// 	{
// 		for (int i=0; i<20; i++)
// 		{
// 			glVertex2f(points[i].x,points[i].y);
// 		}
// 	}
// 	glEnd();
// 
// #define DRAW_ARCH(id1,id2,up) drawArc(id1-1,id2-1,up,20) //1-based numbering
// 
// 	DRAW_ARCH( 1,18,true);
// 	DRAW_ARCH(18,19,false);
// 	DRAW_ARCH(19,20,true);
// 	DRAW_ARCH(20,17,false);
// 	DRAW_ARCH(17,14,true);
// 	DRAW_ARCH(14, 9,false);
// 	DRAW_ARCH( 9, 8,true);
// 	DRAW_ARCH( 8,15,false);
// 	DRAW_ARCH(15,16,true);
// 	DRAW_ARCH(16, 3,false);
// 	DRAW_ARCH( 3, 4,true);
// 	DRAW_ARCH( 4, 7,false);
// 	DRAW_ARCH( 7,10,true);
// 	DRAW_ARCH(10,13,false);
// 	DRAW_ARCH(13,12,true);
// 	DRAW_ARCH(12,11,false);
// 	DRAW_ARCH(11, 6,true);
// 	DRAW_ARCH( 6, 5,false);
// 	DRAW_ARCH( 5, 2,true);
// 	DRAW_ARCH( 2, 1,false);
// 
// 	if(bDrawVerticalLine)
// 		drawVertLine( &points[9], &points[10] );
}

void OpenFile()
{
	OPENFILENAME ofn;
	char szFile[260];

	ZeroMemory(&ofn,sizeof(ofn));
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner=NULL;
	ofn.lpstrFile = szFile;
	ofn.lpstrFile[0]='\0';
	ofn.nMaxFile = sizeof(szFile);
	ofn.lpstrFilter = "All\0*.*\0Text\0*.txt\0";
	ofn.nFilterIndex=1;
	ofn.lpstrFileTitle=NULL;
	ofn.nMaxFileTitle=0;
	ofn.lpstrInitialDir=NULL;
	ofn.Flags = OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST;

	if (GetOpenFileName(&ofn)==TRUE)
	{
		strcpy(lastFileLoaded,ofn.lpstrFile);
		readPermFromFile(lastFileLoaded);
	}
}

void drawSmalPermutation(void)
{
	// draw the horizontal line
// 	glBegin(GL_LINES);
// 	glVertex2f(points[0].x, points[0].y);
// 	glVertex2f(points[9].x, points[9].y);
// 	glEnd();
// 
// 	// draw the point numbering
// 	setFont("helvetica",10);
// 	for (int i=0; i<10; i++)
// 	{
// 		drawStr(points[i].x+5, points[i].y+5,"%d",i+1);
// 	}
// 
// 	// draw a small circle for each point
// 	glBegin(GL_POINTS);
// 	{
// 		for (int i=0; i<10; i++)
// 		{
// 			glVertex2f(points[i].x,points[i].y);
// 		}
// 	}
// 	glEnd();
// 
// 	DRAW_ARCH(1,8,true);
// 	DRAW_ARCH(8,5,false);
// 	DRAW_ARCH(5,4,true);
// 	DRAW_ARCH(4,9,false);
// 	DRAW_ARCH(9,10,true);
// 	DRAW_ARCH(10,3,false);
// 	DRAW_ARCH(3,6,true);
// 	DRAW_ARCH(6,7,false);
// 	DRAW_ARCH(7,2,true);
// 	DRAW_ARCH(2,1,false);
// 
// 	if(bDrawVerticalLine)
// 		drawVertLine( &points[5], &points[6] );
}

/* process menu option 'op' */
void menu(int op) 
{

	switch(op) 
	{
	case 1:
// 		gNumPoints=10;
// 		initPoints();
// 		bDrawBigPermutation=false;
		Reload();
		//initPoints();
		glutPostRedisplay();
		break;	
	case 2:
// 		gNumPoints=20;
// 		initPoints();
// 		bDrawBigPermutation=true;
		//readPermFromFile("planar10.txt");
		//initPoints();
		OpenFile();
		glutPostRedisplay();
		break;
	case 3:
		readPermFromFile("planar20.txt");
		//initPoints();
		glutPostRedisplay();
		break;
	case 'Q':
	case 'q':
		exit(0);
	}
}

/* executed when a regular key is pressed */
void keyboardDown(unsigned char key, int x, int y) {

	switch(key) 
	{
	case 'Q':
	case 'q':
	case  27:   // ESC
		exit(0);
	}
}

/* executed when a regular key is released */
void keyboardUp(unsigned char key, int x, int y) {

}

/* executed when a special key is pressed */
void keyboardSpecialDown(int k, int x, int y) {

}

/* executed when a special key is released */
void keyboardSpecialUp(int k, int x, int y) {

}

/* reshaped window */
void reshape(int width, int height) {

	GLfloat fieldOfView = 90.0f;
	glViewport (0, 0, (GLsizei) width, (GLsizei) height);
	window.height = height;
	window.width = width;

	glMatrixMode (GL_PROJECTION);
	glLoadIdentity();
	// gluPerspective(fieldOfView, (GLfloat) width/(GLfloat) height, 0.1, 500.0);
	glOrtho(-width*0.5f,width*0.5f,-height*0.5f,height*0.5f,0,1);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	initPoints();
	glutPostRedisplay();
}

/* executed when button 'button' is put into state 'state' at screen position ('x', 'y') */
void mouseClick(int button, int state, int x, int y) {

}

/* executed when the mouse moves to position ('x', 'y') */
void mouseMotion(int x, int y) {

}

/* render the scene */
void draw() {

	glClear(GL_COLOR_BUFFER_BIT/* | GL_DEPTH_BUFFER_BIT*/);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	
	glColor3f(0.0f,0.0f,0.0f);
	
	// draw some title
	setFont("helvetica",18);
	drawStr(-((window.width/2)-20),(window.height/2)-40,
		"Planar Permutation with %d points", perm.NumPoints());
	

	/*if(bDrawBigPermutation)
		drawBigPermutation();
	else
		drawSmalPermutation();*/
	drawPermutation();

	glFlush();
	glutSwapBuffers();
}

/* executed when program is idle */
void idle() { 

}

/* initialize OpenGL settings */
void initGL(int width, int height) {

	reshape(width, height);

	glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
	glClearDepth(1.0f);

	//glEnable(GL_DEPTH_TEST);
	//glDepthFunc(GL_LEQUAL);
	glDisable(GL_DEPTH_TEST);
	glPointSize(5.0f);
	glEnable(GL_POINT_SMOOTH);

	setFont("helvetica",18);
}

/* initialize GLUT settings, register callbacks, enter main loop */
int main(int argc, char** argv) {

	glutInit(&argc, argv);

	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
	glutInitWindowPosition(100, 100);
	glutCreateWindow("Planar permutation");

	// register glut call backs
	glutKeyboardFunc(keyboardDown);
	glutKeyboardUpFunc(keyboardUp);
	glutSpecialFunc(keyboardSpecialDown);
	glutSpecialUpFunc(keyboardSpecialUp);
	glutMouseFunc(mouseClick);
	glutMotionFunc(mouseMotion);
	glutReshapeFunc(reshape);
	glutDisplayFunc(draw);  
	glutIdleFunc(idle);
	glutIgnoreKeyRepeat(true); // ignore keys held down


	
	// create a sub menu 
// 	int subMenu = glutCreateMenu(menu);
// 	glutAddMenuEntry("Do nothing", 0);
// 	glutAddMenuEntry("Really Quit", 'q');

	// create main "right click" menu
	glutCreateMenu(menu);
	glutAddMenuEntry("Reload", 1);
	glutAddMenuEntry("Open File", 2);
	glutAddMenuEntry("test3", 3);
	glutAddMenuEntry("Quit", 'q');
	glutAttachMenu(GLUT_RIGHT_BUTTON);

	initGL(800, 600);

	if( argc<3 )
	{
		initDefaultPerm();
	}
	else
	{
		if( argv[1][0]=='-' && argv[1][1]=='f' )
		{
			strcpy(lastFileLoaded,argv[2]);
			readPermFromFile( lastFileLoaded /*"planar20.txt"*/);
		}
	}
	
	glutMainLoop();
	return 0;
}